package cn.donting.jme.edit.state;

import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.math.ColorRGBA;
import com.jme3.post.SceneProcessor;
import com.jme3.profile.AppProfiler;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.Renderer;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.system.JmeSystem;
import com.jme3.texture.FrameBuffer;
import com.jme3.util.BufferUtils;
import com.jme3.util.Screenshots;
import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import lombok.extern.slf4j.Slf4j;

import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;

/**
 * fx Canvas 同步 State
 * @see com.jme3.app.state.ScreenshotAppState
 */
@Slf4j
public class CanvasSyncAppState extends AbstractAppState implements SceneProcessor {
    private Renderer renderer;
    private RenderManager rm;
    private ByteBuffer outBuf;
    private int width, height;

    private ViewPort last;

    /**
     * fx Canvas
     */
    private  Canvas canvas;

    /**
     * 同步间隔时间
     * 1s 同步 30次
     * 30fps
     */
    public long time=1000/30;

    public CanvasSyncAppState(Canvas canvas) {
        this.canvas=canvas;
    }


    @Override
    public void initialize(AppStateManager stateManager, Application app) {
        if (!super.isInitialized()) {

            List<ViewPort> vps = app.getRenderManager().getPostViews();
            last = vps.get(vps.size() - 1);
            last.addProcessor(this);
        }

        super.initialize(stateManager, app);
    }

    /**
     * Clean up this AppState during the first update after it gets detached.
     * <p>
     * Because each ScreenshotAppState is also a SceneProcessor (in addition to
     * being an AppState) this method is also invoked when the SceneProcessor
     * get removed from its ViewPort, leading to an indirect recursion:
     * <ol><li>AppStateManager invokes ScreenshotAppState.cleanup()</li>
     * <li>cleanup() invokes ViewPort.removeProcessor()</li>
     * <li>removeProcessor() invokes ScreenshotAppState.cleanup()</li>
     * <li>... and so on.</li>
     * </ol>
     * <p>
     * In order to break this recursion, this method only removes the
     * SceneProcessor if it has not previously been removed.
     * <p>
     * A better design would have the AppState and SceneProcessor be 2 distinct
     * objects, but doing so now might break applications that rely on them
     * being a single object.
     */
    @Override
    public void cleanup() {
        ViewPort viewPort = last;
        if (viewPort != null) {
            last = null;
            viewPort.removeProcessor(this); // XXX indirect recursion!
        }
        super.cleanup();
    }

    @Override
    public void initialize(RenderManager rm, ViewPort vp) {
        renderer = rm.getRenderer();
        this.rm = rm;
        reshape(vp, vp.getCamera().getWidth(), vp.getCamera().getHeight());
    }

    @Override
    public boolean isInitialized() {
        return super.isInitialized() && renderer != null;
    }

    @Override
    public void reshape(ViewPort vp, int w, int h) {
        outBuf = BufferUtils.createByteBuffer(w * h * 4);
        width = w;
        height = h;
        log.debug("resize:{}-{}",w,h);
    }

    @Override
    public void preFrame(float tpf) {
        // do nothing
    }

    @Override
    public void postQueue(RenderQueue rq) {
        // do nothing
    }

    /**
     * 上一次同步时间
     */
    private long lastSyncTime=System.currentTimeMillis();
    @Override
    public void postFrame(FrameBuffer out) {
        long now = System.currentTimeMillis();
        if (now-lastSyncTime>time){
            lastSyncTime=now;
            Camera curCamera = rm.getCurrentCamera();
            int viewX = (int) (curCamera.getViewPortLeft() * curCamera.getWidth());
            int viewY = (int) (curCamera.getViewPortBottom() * curCamera.getHeight());
            int viewWidth = (int) ((curCamera.getViewPortRight() - curCamera.getViewPortLeft()) * curCamera.getWidth());
            int viewHeight = (int) ((curCamera.getViewPortTop() - curCamera.getViewPortBottom()) * curCamera.getHeight());
            renderer.setViewPort(0, 0, width, height);
            renderer.readFrameBuffer(out, outBuf);
            renderer.setViewPort(viewX, viewY, viewWidth, viewHeight);
            try {
                syncImage();
            } catch (IOException ex) {
                log.error(ex.getMessage(),ex);
            }
        }
    }

    @Override
    public void setProfiler(AppProfiler profiler) {
        // not implemented
    }

    /**
     *  Called by postFrame() once the screen has been captured to outBuf.
     *
     * @throws IOException if an I/O error occurs
     */
    protected void syncImage() throws IOException {

        BufferedImage awtImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
        Screenshots.convertScreenShot2(outBuf.asIntBuffer(), awtImage);
        awtImage = verticalFlip(awtImage);

        WritableImage writableImage = new WritableImage(width, height);
        SwingFXUtils.toFXImage(awtImage,writableImage);
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                //更新JavaFX的主线程的代码放在此处
//                System.out.println("------------更新JavaFX的主线程的代码放在此处");
                GraphicsContext gc = canvas.getGraphicsContext2D();
                gc.clearRect(0,0,canvas.getWidth(),canvas.getHeight());
                gc.drawImage(writableImage,0,0);
                gc.setStroke(Color.WHITE);
                gc.strokeText(canvas.getWidth()+"x"+canvas.getHeight(),0,20);
            }
        });


    }
    private static BufferedImage verticalFlip(BufferedImage original) {
        AffineTransform tx = AffineTransform.getScaleInstance(1, -1);
        tx.translate(0, -original.getHeight());
        AffineTransformOp transformOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
        BufferedImage awtImage = new BufferedImage(original.getWidth(), original.getHeight(), BufferedImage.TYPE_INT_BGR);
        Graphics2D g2d = awtImage.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
                RenderingHints.VALUE_RENDER_SPEED);
        g2d.drawImage(original, transformOp, 0, 0);
        g2d.dispose();
        return awtImage;
    }


}
